﻿using Fungus;
using System;
using System.Reflection;
using System.Reflection.Emit;
using UnityEngine.Events;

namespace CheatMenu.Utils
{
    public static class ReflectionUtil
    {
        //Slow reflection
        //public static T GetField<T>(object instance, string fieldName)
        //{
        //    return (T)instance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance).GetValue(instance);
        //}

        //Fast reflection (up to 500 times faster, after delegate has been created)
        public static Func<S, T> CreateGetter<S, T>(string fieldName)
        {
            FieldInfo field = typeof(S).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
            string methodName = field.ReflectedType.FullName + ".get_" + field.Name;
            DynamicMethod setterMethod = new DynamicMethod(methodName, typeof(T), new Type[1] { typeof(S) }, true);
            ILGenerator gen = setterMethod.GetILGenerator();
            if (field.IsStatic)
            {
                gen.Emit(OpCodes.Ldsfld, field);
            }
            else
            {
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldfld, field);
            }
            gen.Emit(OpCodes.Ret);
            return (Func<S, T>)setterMethod.CreateDelegate(typeof(Func<S, T>));
        }

        public static Action<S, T> CreateSetter<S, T>(string fieldName)
        {
            FieldInfo field = typeof(S).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
            string methodName = field.ReflectedType.FullName + ".set_" + field.Name;
            DynamicMethod setterMethod = new DynamicMethod(methodName, null, new Type[2] { typeof(S), typeof(T) }, true);
            ILGenerator gen = setterMethod.GetILGenerator();
            if (field.IsStatic)
            {
                gen.Emit(OpCodes.Ldarg_1);
                gen.Emit(OpCodes.Stsfld, field);
            }
            else
            {
                gen.Emit(OpCodes.Ldarg_0);
                gen.Emit(OpCodes.Ldarg_1);
                gen.Emit(OpCodes.Stfld, field);
            }
            gen.Emit(OpCodes.Ret);
            return (Action<S, T>)setterMethod.CreateDelegate(typeof(Action<S, T>));
        }

        //================================================================
        //InvokeEvent
        //================================================================
        //InvokeEvent->invokeType InvokeType
        private static Func<InvokeEvent, InvokeType> invokeTypeFromInvokeEvent;
        public static InvokeType GetInvokeTypeFromInvokeEvent(InvokeEvent condition)
        {
            if (invokeTypeFromInvokeEvent == null)
                invokeTypeFromInvokeEvent = CreateGetter<InvokeEvent, InvokeType>("invokeType");

            return invokeTypeFromInvokeEvent(condition);
        }

        //InvokeEvent->staticEvent UnityEvent
        private static Func<InvokeEvent, UnityEvent> staticEventFromInvokeEvent;
        public static UnityEvent GetStaticEventFromInvokeEvent(InvokeEvent condition)
        {
            if (staticEventFromInvokeEvent == null)
                staticEventFromInvokeEvent = CreateGetter<InvokeEvent, UnityEvent>("staticEvent");

            return staticEventFromInvokeEvent(condition);
        }

        //InvokeEvent->booleanEvent BooleanEvent
        private static Func<InvokeEvent, InvokeEvent.BooleanEvent> booleanEventFromInvokeEvent;
        public static InvokeEvent.BooleanEvent GetBooleanEventFromInvokeEvent(InvokeEvent condition)
        {
            if (booleanEventFromInvokeEvent == null)
                booleanEventFromInvokeEvent = CreateGetter<InvokeEvent, InvokeEvent.BooleanEvent>("booleanEvent");

            return booleanEventFromInvokeEvent(condition);
        }

        //InvokeEvent->integerEvent IntegerEvent
        private static Func<InvokeEvent, InvokeEvent.IntegerEvent> integerEventFromInvokeEvent;
        public static InvokeEvent.IntegerEvent GetIntegerEventFromInvokeEvent(InvokeEvent condition)
        {
            if (integerEventFromInvokeEvent == null)
                integerEventFromInvokeEvent = CreateGetter<InvokeEvent, InvokeEvent.IntegerEvent>("integerEvent");

            return integerEventFromInvokeEvent(condition);
        }

        //InvokeEvent->floatEvent FloatEvent
        private static Func<InvokeEvent, InvokeEvent.FloatEvent> floatEventFromInvokeEvent;
        public static InvokeEvent.FloatEvent GetFloatEventFromInvokeEvent(InvokeEvent condition)
        {
            if (floatEventFromInvokeEvent == null)
                floatEventFromInvokeEvent = CreateGetter<InvokeEvent, InvokeEvent.FloatEvent>("floatEvent");

            return floatEventFromInvokeEvent(condition);
        }

        //InvokeEvent->stringEvent StringEvent
        private static Func<InvokeEvent, InvokeEvent.StringEvent> stringEventFromInvokeEvent;
        public static InvokeEvent.StringEvent GetStringEventFromInvokeEvent(InvokeEvent condition)
        {
            if (stringEventFromInvokeEvent == null)
                stringEventFromInvokeEvent = CreateGetter<InvokeEvent, InvokeEvent.StringEvent>("stringEvent");

            return stringEventFromInvokeEvent(condition);
        }

        //================================================================
        //SetActive
        //================================================================
        //SetActive->_targetGameObject GameObjectData
        private static Func<SetActive, GameObjectData> targetGameObjectFromSetActive;
        public static GameObjectData GetTargetGameObjectFromSetActive(SetActive condition)
        {
            if (targetGameObjectFromSetActive == null)
                targetGameObjectFromSetActive = CreateGetter<SetActive, GameObjectData>("_targetGameObject");

            return targetGameObjectFromSetActive(condition);
        }

        //SetActive->activeState BooleanData
        private static Func<SetActive, BooleanData> activeStateFromSetActive;
        public static BooleanData GetActiveStateFromSetActive(SetActive condition)
        {
            if (activeStateFromSetActive == null)
                activeStateFromSetActive = CreateGetter<SetActive, BooleanData>("activeState");

            return activeStateFromSetActive(condition);
        }

        //================================================================
        //SetText
        //================================================================
        //SetText->text StringData
        private static Func<SetText, StringData> textFromSetText;
        public static StringData GetTextFromSetText(SetText condition)
        {
            if (textFromSetText == null)
                textFromSetText = CreateGetter<SetText, StringData>("text");

            return textFromSetText(condition);
        }

        //================================================================
        //RandomInteger
        //================================================================
        //RandomInteger->variable IntegerVariable
        private static Func<RandomInteger, IntegerVariable> variableFromRandomInteger;
        public static IntegerVariable GetVariableFromRandomInteger(RandomInteger randomInteger)
        {
            if (variableFromRandomInteger == null)
                variableFromRandomInteger = CreateGetter<RandomInteger, IntegerVariable>("variable");

            return variableFromRandomInteger(randomInteger);
        }

        //RandomInteger->minValue IntegerData
        private static Func<RandomInteger, IntegerData> minValueFromRandomInteger;
        public static IntegerData GetMinValueFromRandomInteger(RandomInteger randomInteger)
        {
            if (minValueFromRandomInteger == null)
                minValueFromRandomInteger = CreateGetter<RandomInteger, IntegerData>("minValue");

            return minValueFromRandomInteger(randomInteger);
        }

        //RandomInteger->maxValue IntegerData
        private static Func<RandomInteger, IntegerData> maxValueFromRandomInteger;
        public static IntegerData GetMaxValueFromRandomInteger(RandomInteger randomInteger)
        {
            if (maxValueFromRandomInteger == null)
                maxValueFromRandomInteger = CreateGetter<RandomInteger, IntegerData>("maxValue");

            return maxValueFromRandomInteger(randomInteger);
        }

        //RandomInteger->minValue IntegerData
        private static Action<RandomInteger, IntegerData> minValueFromRandomIntegerSetter;
        public static void SetMinValueFromRandomInteger(RandomInteger randomInteger, IntegerData value)
        {
            if (minValueFromRandomIntegerSetter == null)
                minValueFromRandomIntegerSetter = CreateSetter<RandomInteger, IntegerData>("minValue");

            minValueFromRandomIntegerSetter(randomInteger, value);
        }

        //================================================================
        //SaveVariable
        //================================================================
        //SaveVariable->key string
        private static Func<SaveVariable, string> keyFromSaveVariable;
        public static string GetKeyFromSaveVariable(SaveVariable condition)
        {
            if (keyFromSaveVariable == null)
                keyFromSaveVariable = CreateGetter<SaveVariable, string>("key");

            return keyFromSaveVariable(condition);
        }

        //================================================================
        //LoadVariable
        //================================================================
        //LoadVariable->key string
        private static Func<LoadVariable, string> keyFromLoadVariable;
        public static string GetKeyFromLoadVariable(LoadVariable condition)
        {
            if (keyFromLoadVariable == null)
                keyFromLoadVariable = CreateGetter<LoadVariable, string>("key");

            return keyFromLoadVariable(condition);
        }

        //================================================================
        //Call
        //================================================================
        //Call->targetBlock Block
        private static Func<Call, Block> targetBlockFromCall;
        public static Block GetTargetBlockFromCall(Call condition)
        {
            if (targetBlockFromCall == null)
                targetBlockFromCall = CreateGetter<Call, Block>("targetBlock");

            return targetBlockFromCall(condition);
        }

        //Call->targetFlowchart Flowchart
        private static Func<Call, Flowchart> targetFlowchartFromCall;
        public static Flowchart GetTargetFlowchartFromCall(Call condition)
        {
            if (targetFlowchartFromCall == null)
                targetFlowchartFromCall = CreateGetter<Call, Flowchart>("targetFlowchart");

            return targetFlowchartFromCall(condition);
        }

        //================================================================
        //Say
        //================================================================
        //Say->character Character
        private static Func<Say, Character> characterFromSay;
        public static Character GetCharacterFromSay(Say condition)
        {
            if (characterFromSay == null)
                characterFromSay = CreateGetter<Say, Character>("character");

            return characterFromSay(condition);
        }

        //Say->storyText string
        private static Func<Say, string> storyTextFromSay;
        public static string GetStoryTextFromSay(Say condition)
        {
            if (storyTextFromSay == null)
                storyTextFromSay = CreateGetter<Say, string>("storyText");

            return storyTextFromSay(condition);
        }

        //================================================================
        //LoadScene
        //================================================================
        //LoadScene->_sceneName StringData
        private static Func<LoadScene, StringData> sceneNameFromLoadScene;
        public static StringData GetSceneNameFromLoadScene(LoadScene condition)
        {
            if (sceneNameFromLoadScene == null)
                sceneNameFromLoadScene = CreateGetter<LoadScene, StringData>("_sceneName");

            return sceneNameFromLoadScene(condition);
        }

        //================================================================
        //Menu
        //================================================================
        //Menu->targetBlock Block
        private static Func<Menu, Block> targetBlockFromMenu;
        public static Block GetTargetBlockFromMenu(Menu condition)
        {
            if (targetBlockFromMenu == null)
                targetBlockFromMenu = CreateGetter<Menu, Block>("targetBlock");

            return targetBlockFromMenu(condition);
        }

        //Menu->text string
        private static Func<Menu, string> textFromMenu;
        public static string GetTextFromMenu(Menu condition)
        {
            if (textFromMenu == null)
                textFromMenu = CreateGetter<Menu, string>("text");

            return textFromMenu(condition);
        }

        //================================================================
        //SetVariable
        //================================================================
        //SetVariable->setOperator SetOperator
        private static Func<SetVariable, SetOperator> setOperatorFromSetVariable;
        public static SetOperator GetSetOperatorFromSetVariable(SetVariable condition)
        {
            if (setOperatorFromSetVariable == null)
                setOperatorFromSetVariable = CreateGetter<SetVariable, SetOperator>("setOperator");

            return setOperatorFromSetVariable(condition);
        }

        //SetVariable->variable Variable
        private static Func<SetVariable, Variable> variableFromSetVariable;
        public static Variable GetVariableFromSetVariable(SetVariable condition)
        {
            if (variableFromSetVariable == null)
                variableFromSetVariable = CreateGetter<SetVariable, Variable>("variable");

            return variableFromSetVariable(condition);
        }

        //SetVariable->integerData IntegerData
        private static Func<SetVariable, IntegerData> integerDataFromSetVariable;
        public static IntegerData GetIntegerDataFromSetVariable(SetVariable condition)
        {
            if (integerDataFromSetVariable == null)
                integerDataFromSetVariable = CreateGetter<SetVariable, IntegerData>("integerData");

            return integerDataFromSetVariable(condition);
        }

        //SetVariable->booleanData BooleanData
        private static Func<SetVariable, BooleanData> booleanDataFromSetVariable;
        public static BooleanData GetBooleanDataFromSetVariable(SetVariable condition)
        {
            if (booleanDataFromSetVariable == null)
                booleanDataFromSetVariable = CreateGetter<SetVariable, BooleanData>("booleanData");

            return booleanDataFromSetVariable(condition);
        }

        //SetVariable->stringData StringDataMulti
        private static Func<SetVariable, StringDataMulti> stringDataFromSetVariable;
        public static StringDataMulti GetStringDataFromSetVariable(SetVariable condition)
        {
            if (stringDataFromSetVariable == null)
                stringDataFromSetVariable = CreateGetter<SetVariable, StringDataMulti>("stringData");

            return stringDataFromSetVariable(condition);
        }

        //SetVariable->floatData FloatData
        private static Func<SetVariable, FloatData> floatDataFromSetVariable;
        public static FloatData GetFloatDataFromSetVariable(SetVariable condition)
        {
            if (floatDataFromSetVariable == null)
                floatDataFromSetVariable = CreateGetter<SetVariable, FloatData>("floatData");

            return floatDataFromSetVariable(condition);
        }

        //================================================================
        //VariableCondition
        //================================================================
        //VariableCondition->compareOperator CompareOperator
        private static Func<VariableCondition, CompareOperator> compareOperatorFromVariableCondition;
        public static CompareOperator GetCompareOperatorFromVariableCondition(VariableCondition condition)
        {
            if (compareOperatorFromVariableCondition == null)
                compareOperatorFromVariableCondition = CreateGetter<VariableCondition, CompareOperator>("compareOperator");

            return compareOperatorFromVariableCondition(condition);
        }

        //VariableCondition->variable Variable
        private static Func<VariableCondition, Variable> variableFromVariableCondition;
        public static Variable GetVariableFromVariableCondition(VariableCondition condition)
        {

            if (variableFromVariableCondition == null)
                variableFromVariableCondition = CreateGetter<VariableCondition, Variable>("variable");

            return variableFromVariableCondition(condition);
        }

        //VariableCondition->integerData IntegerData
        private static Func<VariableCondition, IntegerData> integerDataFromVariableCondition;
        public static IntegerData GetIntegerDataFromVariableCondition(VariableCondition condition)
        {
            if (integerDataFromVariableCondition == null)
                integerDataFromVariableCondition = CreateGetter<VariableCondition, IntegerData>("integerData");

            return integerDataFromVariableCondition(condition);
        }

        //VariableCondition->booleanData BooleanData
        private static Func<VariableCondition, BooleanData> booleanDataFromVariableCondition;
        public static BooleanData GetBooleanDataFromVariableCondition(VariableCondition condition)
        {
            if (booleanDataFromVariableCondition == null)
                booleanDataFromVariableCondition = CreateGetter<VariableCondition, BooleanData>("booleanData");

            return booleanDataFromVariableCondition(condition);
        }

        //VariableCondition->stringData StringDataMulti
        private static Func<VariableCondition, StringDataMulti> stringDataFromVariableCondition;
        public static StringDataMulti GetStringDataFromVariableCondition(VariableCondition condition)
        {
            if (stringDataFromVariableCondition == null)
                stringDataFromVariableCondition = CreateGetter<VariableCondition, StringDataMulti>("stringData");

            return stringDataFromVariableCondition(condition);
        }

        //VariableCondition->floatData FloatData
        private static Func<VariableCondition, FloatData> floatDataFromVariableCondition;
        public static FloatData GetFloatDataFromVariableCondition(VariableCondition condition)
        {
            if (floatDataFromVariableCondition == null)
                floatDataFromVariableCondition = CreateGetter<VariableCondition, FloatData>("floatData");

            return floatDataFromVariableCondition(condition);
        }

        //================================================================
        //Writer
        //================================================================
        //Writer->writingSpeed float
        private static Action<Writer, float> writingSpeedOfWriter;
        public static void SetWritingSpeedOfWriter(Writer writer, float val)
        {
            if (writingSpeedOfWriter == null)
                writingSpeedOfWriter = CreateSetter<Writer, float>("writingSpeed");

            writingSpeedOfWriter(writer, val);
        }

        //Writer->punctuationPause float
        private static Action<Writer, float> punctiationPauseOfWriter;
        public static void SetPunctiationPauseOfWriter(Writer writer, float val)
        {
            if (punctiationPauseOfWriter == null)
                punctiationPauseOfWriter = CreateSetter<Writer, float>("punctuationPause");

            punctiationPauseOfWriter(writer, val);
        }
        //================================================================
        //Wait
        //================================================================
        //Wait->_duration FloatData
        private static Action<Wait, FloatData> waitDuration;
        public static void SetWaitDuration(Wait wait, FloatData val)
        {
            if (waitDuration == null)
                waitDuration = CreateSetter<Wait, FloatData>("_duration");

            waitDuration(wait, val);
        }
        //================================================================
        //FadeScreen
        //================================================================
        //FadeScreen->duration float
        private static Action<FadeScreen, float> fadeDuration;
        public static void SetFadeDuration(FadeScreen fade, float val)
        {
            if (fadeDuration == null)
                fadeDuration = CreateSetter<FadeScreen, float>("duration");

            fadeDuration(fade, val);
        }
        //FadeScreen->waitUntilFinished bool
        private static Action<FadeScreen, bool> fadeWaitUntilFinished;
        public static void SetFadeWaitUntilFinished(FadeScreen fade, bool val)
        {
            if (fadeWaitUntilFinished == null)
                fadeWaitUntilFinished = CreateSetter<FadeScreen, bool>("waitUntilFinished");

            fadeWaitUntilFinished(fade, val);
        }
        //FadeScreen->targetAlpha float
        private static Action<FadeScreen, float> fadeTargetAlpha;
        public static void SetFadeTargetAlpha(FadeScreen fade, float val)
        {
            if (fadeTargetAlpha == null)
                fadeTargetAlpha = CreateSetter<FadeScreen, float>("targetAlpha");

            fadeTargetAlpha(fade, val);
        }
        //================================================================
        //SceneLoader
        //================================================================
        //SceneLoader->sceneToLoad string
        private static Func<SceneLoader, string> sceneToLoad;
        public static string GetSceneToLoad(SceneLoader sl)
        {
            if (sceneToLoad == null)
                sceneToLoad = CreateGetter<SceneLoader, string>("sceneToLoad");

            return sceneToLoad(sl);
        }
    }
}
